import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class MainForm extends JDialog implements ActionListener {
    //数字矩阵
    private final int[][] m_Matrix = new int[9][9];
    private final JButton openButton;

    /**
     * 界面初始化
     */
    public MainForm() {
        setTitle("数独求解");
        setBounds(100, 100, 400, 375);
        getContentPane().setLayout(new BorderLayout());
        //按钮面板
        JPanel buttonPane = new JPanel();
        buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
        openButton = new JButton("打开数据文件");
        openButton.addActionListener(this);
        buttonPane.add(openButton);
        getContentPane().add("South", buttonPane);
        //数字面板
        JPanel panel = new JPanel();
        panel.setBorder(new BevelBorder(BevelBorder.LOWERED, null, null, null, null));
        panel.setBounds(10, 10, 354, 270);
        panel.setLayout(new GridLayout(9, 9, 1, 1));
        for (int i = 0; i < 81; i++) {
            JTextField field = new JTextField();
            field.setEditable(false);
            field.setHorizontalAlignment(SwingConstants.CENTER);
            field.setText("0");
            panel.add(field);
        }
        getContentPane().add("Center",panel);
    }

    /**
     * 启动方法
     * 题目的格式见example.txt，空位写0，回车换行
     */
    public static void main(String[] args) {
        try {
            MainForm dialog = new MainForm();
            dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
            dialog.setVisible(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 填充矩阵的动态规划算法
     *
     * @param currentMatrix 当前矩阵
     * @param startIndex    尝试填充的位置序号，可以据此算出行列号
     */
    private void fillMatrix(int[][] currentMatrix, int startIndex) {
        int nextIndex;// 待插入数字的索引
        int next_i = 9, next_j = 9;// 待插入的位置
        //从当前位置开始逐个向前尝试
        for (nextIndex = startIndex; nextIndex <= 80; nextIndex++) {
            int pos_x = nextIndex / 9, pos_y = nextIndex % 9;//尝试填入位置的行列坐标
            if (currentMatrix[pos_x][pos_y] == 0) {// 0表示这个位置还没有填写过
                next_i = pos_x;
                next_j = pos_y;
                break;
            }
        }
        if (nextIndex > 80) {// 到达矩阵末尾了，打印结果，结束计算
            this.matrixToTable();
            return;
        }
        for (int num = 1; num <= 9; num++) {
            if (this.check(currentMatrix, next_i, next_j, num)) {// 如果此位置尝试插入的数字合法
                currentMatrix[next_i][next_j] = num;
                fillMatrix(currentMatrix, nextIndex + 1);//递归尝试下一位置
                currentMatrix[next_i][next_j] = 0;//恢复为未填写状态，以便检验下一个尝试的数字，此句不可少
            }
        }

    }

    /**
     * 检查(i,j)位的num在矩阵m中是否合法
     */
    private boolean check(int[][] matrix, int current_i, int current_j, int num) {
        for (int i = 0; i < 9; i++) {// 第一步检查所在行
            if (matrix[i][current_j] == num) {
                return false;
            }
        }
        for (int j = 0; j < 9; j++) {// 第二步检查所在列
            if (matrix[current_i][j] == num) {
                return false;
            }
        }

        // 按照数独规则，第三步是检查所在的3*3正方形。思路是先找出位置所在方形的中心点，然后获取包括中心的9个元素，再检查重复
        // 所以要建立一个012->1，345->4，678->7的映射，这个可以单独写一个getCenter来完成
        int center_i = getCenter(current_i);
        int center_j = getCenter(current_j);

        int[] subArray = new int[9];// subarray表示以center为中心的一个3x3矩阵，在这个矩阵里面数字亦不可重复
        subArray[0] = matrix[center_i - 1][center_j - 1];
        subArray[1] = matrix[center_i][center_j - 1];
        subArray[2] = matrix[center_i + 1][center_j - 1];
        subArray[3] = matrix[center_i - 1][center_j];
        subArray[4] = matrix[center_i][center_j];
        subArray[5] = matrix[center_i + 1][center_j];
        subArray[6] = matrix[center_i - 1][center_j + 1];
        subArray[7] = matrix[center_i][center_j + 1];
        subArray[8] = matrix[center_i + 1][center_j + 1];
        for (int i = 0; i < 9; i++) {// 搜索所在小正方形有无重复数字
            if (subArray[i] == num) {
                return false;
            }
        }
        return true;
    }

    private int getCenter(int n) {
        switch (n) {
            case 0:
            case 1:
            case 2:
                return 1;
            case 3:
            case 4:
            case 5:
                return 4;
            case 6:
            case 7:
            case 8:
                return 7;
            default:
                return -1;
        }
    }

    /**
     * 打印结果到文本框
     */
    private void matrixToTable() {
        JPanel numberPanel = (JPanel) (this.getContentPane().getComponent(1));
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                JTextField field = (JTextField) (numberPanel.getComponent(i * 9 + j));
                field.setText(String.valueOf(this.m_Matrix[i][j]));
            }
        }
    }

    @Override
    public void actionPerformed(ActionEvent arg0) {
        if (arg0.getSource() == openButton) {
            openProblemFile(arg0);
        }
    }

    /**
     * 按钮单击事件
     */
    protected void openProblemFile(ActionEvent arg0) {
        JFileChooser chooser = new JFileChooser();
        chooser.showOpenDialog(this);
        File file = chooser.getSelectedFile();
        if (file == null) {
            return;
        }
        try {
            BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
            for (int i = 0; i < 9; i++) {
                String str = bufferedReader.readLine();
                for (int j = 0; j < str.length(); j++) {
                    m_Matrix[i][j] = Integer.parseInt(str.substring(j, j + 1));
                }
            }
            this.fillMatrix(m_Matrix, 0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
